﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;


namespace TreeViewEx
{
	public delegate void DragEventHandler(object sender, DragEventData e, CancelEvent cancel);

    public class TreeViewEx : TreeView
    {
		private TreeNode prevTarget = null;
        public event DragEventHandler DragStart;
        public event DragEventHandler DragComplete;
        private DragEventData dragData = new DragEventData();

		public TreeViewEx()
		{
			this.AllowDrop = true;
		}

		protected override void OnItemDrag(ItemDragEventArgs e)
		{
            if (DragStart != null && typeof(TreeNode) == e.Item.GetType())
            {
                dragData.SourceNode = (TreeNode)e.Item;
                CancelEvent cancel = new CancelEvent();
                DragStart.Invoke(this, dragData, cancel);
                if (!cancel.Cancel)
                {
                    DoDragDrop(e.Item, DragDropEffects.Move | DragDropEffects.Copy);
                }
            }
		}

		protected override void OnDragOver(DragEventArgs drgevent)
		{
			//Graphics g = Graphics.FromImage(new Bitmap(100,100));
			//Pen blackPen = new Pen(Color.Black);
			//g.DrawLine(blackPen, 100, 100, 100, 100);

			Point pt = this.PointToClient(new Point(drgevent.X, drgevent.Y));
			TreeNode node = this.GetNodeAt(pt);
			//determine whether to draw a line at the top or a line at the bottom or highlight the node
			if (pt.Y < (node.Bounds.Y + (node.Bounds.Height / 2)- 3))	//above the 6 pixel median
			{
                node.BackColor = Color.DarkBlue;
                dragData.TargetPosition = DropPosition.DropAbove;
			}
			else if (pt.Y > (node.Bounds.Y + (node.Bounds.Height / 2)+ 3))	//below the 6 pixel median
			{
                node.BackColor = Color.LightBlue;
                dragData.TargetPosition = DropPosition.DropBelow;
			}
			else
			{
				node.BackColor = Color.Blue;
                
                dragData.TargetPosition = DropPosition.DropIn;
			}

            dragData.TargetNode = node;

			if (prevTarget != null && prevTarget != node) { prevTarget.BackColor = Color.White; }
			prevTarget = node;

			drgevent.Effect = DragDropEffects.Move;
			//base.OnDragOver(drgevent);
		}

		protected override void OnDragDrop(DragEventArgs drgevent)
		{
			if (prevTarget != null) { prevTarget.BackColor = Color.White; }
            if (DragComplete != null && dragData.SourceNode != dragData.TargetNode && dragData.SourceNode != null && dragData.TargetNode != null)
            {
                CancelEvent cancel = new CancelEvent();
                DragComplete.Invoke(this, dragData, cancel);
            }
		}

		protected override void OnDragEnter(DragEventArgs drgevent)
		{
			base.OnDragEnter(drgevent);
		}

		protected override void OnDragLeave(EventArgs e)
		{
			base.OnDragLeave(e);
		}

		protected override void OnGiveFeedback(GiveFeedbackEventArgs gfbevent)
		{
			base.OnGiveFeedback(gfbevent);
		}
    }

    public enum DropPosition
    {
        DropIn,
        DropAbove,
        DropBelow,
        Unknown
    }

    public class DragEventData
    {
        private TreeNode dragSource = null;
        private TreeNode dragTarget = null;
        private DropPosition targetPosition = DropPosition.Unknown;
        private bool cancel = false;

        public TreeNode SourceNode
        {
            get
            {
                return dragSource;
            }
            set
            {
                dragSource = value;
            }
        }

        public TreeNode TargetNode
        {
            get
            {
                return dragTarget;
            }
            set
            {
                dragTarget = value;
            }
        }

        public DropPosition TargetPosition
        {
            get
            {
                return targetPosition;
            }
            set
            {
                targetPosition = value;
            }
        }
    }

    public class CancelEvent
    {
        private bool cancel = false;

        public bool Cancel
        {
            get
            {
                return cancel;
            }
            set
            {
                cancel = value;
            }
        }
    }
}
